#include <assert.h>
#ifdef WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment (lib, "Ws2_32.lib")
#else
#include <errno.h>
#include <sys/socket.h>
#include <netinet/tcp.h>
#include <string.h>
#endif

#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"

#include <event2/event-config.h>
#include <event2/event.h>
#include <event2/bufferevent.h>
#include <event2/buffer.h>
#include <event2/listener.h>
#include <event2/util.h>

#define ZNET_VER "1.0.1"
#define EVENT_BASE_MT "ZEVENT_BASE_MT"
#define EVENT_CALLBACK_ARG_MT "ZEVENT_CALLBACK_ARG_MT"
#define ZNETEVENT_BV_MT "ZNETEVENT_BV_MT"

typedef struct
{
	struct event_base* base;
	lua_State* loop_L;
	int isSelfBase;
} le_base;

typedef struct
{
	struct event* ev;
	le_base* base;
	int callbackRef;
	double tmout;
	struct timeval timeout;
} le_callback;

typedef struct
{
	struct bufferevent* ev;
	le_base* base;
	int objRef;
}zn_bv;


void free_xn_bv(zn_bv* arg, lua_State* L)
{
	if(arg->base)
	{
		bufferevent_free(arg->ev);
		arg->base = NULL;
		luaL_unref(L, LUA_REGISTRYINDEX, arg->objRef);
	}
}

static int znetevent_bv_gc(lua_State* L)
{
	zn_bv* arg = (zn_bv*)luaL_checkudata(L,1,ZNETEVENT_BV_MT);
	free_xn_bv(arg, L);
	return 0;
}

int znetevent_bv_register(lua_State* L)
{
	luaL_newmetatable(L, ZNETEVENT_BV_MT);
	lua_pushcfunction(L, znetevent_bv_gc);
	lua_setfield(L, -2, "__gc");
	lua_newtable(L);
	lua_pushcfunction(L, znetevent_bv_gc);
	lua_setfield(L, -2, "close");
	lua_setfield(L, -2, "__index");
	lua_pop(L, 1);
	return 0;
}

int getSocketFd(lua_State* L, int idx)
{
	int fd;
	if(lua_isnumber(L, idx))
	{
		fd = lua_tonumber(L, idx);
	}
	else
	{
		luaL_checktype(L, idx, LUA_TUSERDATA);
		lua_getfield(L, idx, "getfd");
		if(lua_isnil(L, -1))
		{
			return luaL_error(L, "Socket type missing 'getfd' method");
		}
		lua_pushvalue(L, idx);
		lua_call(L, 1, 1);
		fd = lua_tointeger(L, -1);
		lua_pop(L, 1);
	}
	return fd;
}
void load_timeval(double time, struct timeval *tv)
{
	tv->tv_sec = (int) time;
	tv->tv_usec = (int)( (time - tv->tv_sec) * 1000000 );
}

le_base* event_base_get(lua_State* L, int idx)
{
	return (le_base*)luaL_checkudata(L, idx, EVENT_BASE_MT);
}

void freeCallbackArgs(le_callback* arg, lua_State* L)
{
	if(arg->base)
	{
		arg->base = NULL;
		event_free(arg->ev);
		luaL_unref(L, LUA_REGISTRYINDEX, arg->callbackRef);
	}
}

void znetevent_callback(int fd, short event, void* p)
{
	le_callback* cb = (le_callback*)p;
	lua_State* L;
	int ret;
	double newTimeout = -1;
	assert(cb);
	if(!cb->base)
	{
		/* Callback has been collected... die */
		/* TODO: What should really be done here... */
		printf("pcall no base\n");
		return;
	}
	assert(cb->base->loop_L);
	L = cb->base->loop_L;
	lua_rawgeti(L, LUA_REGISTRYINDEX, cb->callbackRef);
	lua_pushinteger(L, event);
	if (lua_pcall(L, 1, 2,0)!=0)
	{
		printf("pcall err: %s\n",lua_tostring(L,-1));
		lua_pop(L, 1); /* Pop error message */
	}
	ret = lua_tointeger(L,-2);
	if(lua_isnumber(L, -1))
	{
		newTimeout = lua_tonumber(L, -1);
		if(newTimeout<=0)
		{
			memset(&cb->timeout,0,sizeof(cb->timeout));
		}
		else
		{
			load_timeval(newTimeout,&cb->timeout);
		}
	}
	lua_pop(L, 2);
	if(ret == -1)
	{
		freeCallbackArgs(cb, L);
	}
	else
	{
		struct event *ev = cb->ev;
		int newEvent = ret;
		/* NOTE: Currently, even if new timeout is the same as the old, a new event is setup regardless... */
		if(newEvent != event || newTimeout != cb->tmout)
		{ // Need to hook up new event...
			struct timeval *ptv = &cb->timeout;
			cb->tmout=newTimeout;
			if (newTimeout!=-1)
			{
				load_timeval(newTimeout, ptv);
				if(!cb->timeout.tv_sec && !cb->timeout.tv_usec)
					ptv = NULL;
			}
			else
			{
				ptv = NULL;
			}
			event_del(ev);
			if (newEvent==EV_TIMEOUT)
				fd=-1;
			event_assign(ev, cb->base->base,fd, EV_PERSIST | newEvent, znetevent_callback, cb);
			/* Assume cannot set a new timeout.. */
			event_add(ev, ptv);
		}
	}
}

static int znetevent_cb_gc(lua_State* L)
{
	le_callback* arg = (le_callback*)luaL_checkudata(L, 1, EVENT_CALLBACK_ARG_MT);
	freeCallbackArgs(arg, L);
	return 0;
}

le_callback* event_callback_push(lua_State* L, int baseIdx, int callbackIdx)
{
	le_callback* cb;
	le_base *base = event_base_get(L, baseIdx);
	luaL_checktype(L, callbackIdx, LUA_TFUNCTION);
	cb = (le_callback*)lua_newuserdata(L, sizeof(le_callback));
	luaL_getmetatable(L, EVENT_CALLBACK_ARG_MT);
	lua_setmetatable(L, -2);

	lua_pushvalue(L, callbackIdx);
	cb->callbackRef = luaL_ref(L, LUA_REGISTRYINDEX);
	cb->base = base;
	memset(&cb->timeout, 0, sizeof(cb->timeout));
	return cb;
}

int event_callback_register(lua_State* L)
{
	luaL_newmetatable(L, EVENT_CALLBACK_ARG_MT);
	lua_pushcfunction(L, znetevent_cb_gc);
	lua_setfield(L, -2, "__gc");
	lua_newtable(L);
	lua_pushcfunction(L, znetevent_cb_gc);
	lua_setfield(L, -2, "close");
	lua_setfield(L, -2, "__index");
	lua_pop(L, 1);
	return 0;
}

typedef struct {
	struct bufferevent* ev;
	le_base* base;
} le_bufferevent;

int znetevent_newbase(lua_State* L)
{
	struct event_base* appbase=NULL;
	le_base *base=NULL;
	if (!lua_isnil(L,1) && !lua_isnil(L,2))
	{
		const char* ver;
		appbase=(struct event_base*)lua_topointer(L,1);
		ver=luaL_checkstring(L,2);
		if (strcmp(ver, event_get_version())!=0)
		{
			printf("Libevent version mismatch. znetevent is %s, but app is %s.\n", ver, event_get_version());
			return 0;
		}
	}
	base = (le_base*)lua_newuserdata(L, sizeof(le_base));
	base->loop_L = NULL; /* No running loop */
	if(appbase==NULL)
	{
		base->base = event_base_new();
		base->isSelfBase=1;
	}
	else
	{
		base->base=appbase;
		base->isSelfBase=0;
		base->loop_L = L;
		printf("USE APP EVENT BASE: %s!\n", ZNET_VER);
	}
	luaL_getmetatable(L, EVENT_BASE_MT);
	lua_setmetatable(L, -2);
	return 1;
}

int znetevent_libevent_version(lua_State* L)
{
	lua_pushstring(L, event_get_version());
	return 1;
}

static int znetevent_base_gc(lua_State* L)
{
	le_base *base = event_base_get(L, 1);
	if(base->base) {
		if (base->isSelfBase==1)
		{
			event_base_free(base->base);
		}
		base->base = NULL;
	}
	return 0;
}

/* sock, event, callback, timeout */
static int znetevent_addevent(lua_State* L)
{
	int fd, event;
	le_callback* arg = event_callback_push(L, 1, 4);
	struct timeval *tv = &arg->timeout;
	if(lua_isnil(L, 2) && lua_isnumber(L, 5))
	{
		fd = -1; /* Per event_timer_set.... */
	}
	else
	{
		fd = getSocketFd(L, 2);
	}
	event = luaL_checkinteger(L, 3);
	if(lua_isnumber(L, 5))
	{
		arg->tmout=lua_tonumber(L, 5);
		load_timeval(arg->tmout, tv);
	}
	else
	{
		arg->tmout=-1;
		tv = NULL;
	}

	/* Setup event... */
	arg->ev=event_new(arg->base->base,fd, event | EV_PERSIST, znetevent_callback, arg);
	event_add(arg->ev, tv);
	return 1;
}

static int znetevent_loop(lua_State* L)
{
	int ret;
	le_base *base = event_base_get(L, 1);
	base->loop_L = L;
	ret = event_base_loop(base->base, lua_tointeger(L,2));
	lua_pushinteger(L, ret);
	return 1;
}

static int znetevent_loopexit(lua_State*L)
{
	int ret;
	le_base *base = event_base_get(L, 1);
	struct timeval tv = { 0, 0 };
	if(lua_gettop(L) >= 2) /* Optional timeout before exiting the loop */
	{
		load_timeval(luaL_checknumber(L, 2), &tv);
	}
	ret = event_base_loopexit(base->base, &tv);
	lua_pushinteger(L, ret);
	return 1;
}

static int znetevent_method(lua_State* L)
{
	le_base *base = event_base_get(L, 1);
	lua_pushstring(L, event_base_get_method(base->base));
	return 1;
}

static int znet_recv(int s, char *buf, int len, int flags)
{
	int n;
#ifdef WIN32
	if ((n = recv(s, buf, len, flags)) < 0) {
		if (WSAGetLastError() == WSAEWOULDBLOCK) {
#else
	if ((n = recv(s, buf, len, flags)) < 0) {
		if (errno == EAGAIN || errno==EWOULDBLOCK) {
#endif
			return -2;
		}
		else
			return -1;
	}
	return(n);
}
static int znet_send(int s, const char *buf, int len, int flags)
{
	int n;
#ifdef WIN32
	if ((n = send(s, buf, len, flags)) < 0) {
		if (WSAGetLastError() == WSAEWOULDBLOCK) {
#else
	if ((n = send(s, buf, len, flags)) < 0) {
		if (errno == EAGAIN || errno==EWOULDBLOCK) {
#endif
			return -2;
		}
		else
			return -1;
	}
	return (n);
}

static int znetevent_sendfd(lua_State* L)
{
	size_t len;
	const char* buf;
	int pos,posend,r;
	int fd=getSocketFd(L,1);
	if (fd==-1)
	{
		lua_pushnil(L);
		lua_pushstring(L,"closed");
		return 2;
	}
	buf=luaL_checklstring(L,2,&len);
	pos=luaL_optinteger(L,3,1)-1;
	posend=luaL_optinteger(L,4,len);
	r=znet_send(fd,buf+pos,posend-pos,0);
	if (r<=0)
	{
		lua_pushnil(L);
		lua_pushstring(L,r==-2?"timeout":(r==0?"closed":"unknow error"));
		return 2;
	}
	if (r!=posend-pos)
	{
		lua_pushnil(L);
		lua_pushstring(L,"timeout");
		lua_pushinteger(L,pos+r);
		return 3;
	}
	lua_pushinteger(L,pos+r);
	return 1;
}

static int znetevent_recvfd(lua_State* L)
{
	int fd=getSocketFd(L,1);
	char buf[4096];
	int len,blen,r;
	if (fd==-1)
	{
		lua_pushnil(L);
		lua_pushstring(L,"closed");
		return 2;
	}
	len=luaL_checkinteger(L,2);
	blen=len>4096?4096:len;
	r=znet_recv(fd,buf,blen,0);
	if (len==0 && r==0)
	{
		lua_pushlstring(L,"",0);
		return 1;
	}
	if (r<=0)
	{
		lua_pushnil(L);
		lua_pushstring(L,r==-2?"timeout":(r==0?"closed":"unknow error"));
		return 2;
	}
	if (r<len)
	{
		lua_pushnil(L);
		lua_pushstring(L,"timeout");
		lua_pushlstring(L,buf,r);
		return 3;
	}
	lua_pushlstring(L,buf,r);
	return 1;
}
static luaL_Reg base_funcs[] = {
	{ "addevent", znetevent_addevent },
	{ "loop", znetevent_loop },
	{ "loopexit", znetevent_loopexit },
	{ "method", znetevent_method },
	{ NULL, NULL }
};

static luaL_Reg funcs[] = {
	{ "new", znetevent_newbase },
	{ "libevent_version", znetevent_libevent_version },
	{ "sendfd", znetevent_sendfd },
	{ "recvfd", znetevent_recvfd },
	{ NULL, NULL }
};


typedef struct {
	const char* name;
	int value;
} namedInteger;

static namedInteger consts[] = {
	{"LEAVE", -1},
	{"EV_READ", EV_READ},
	{"EV_WRITE", EV_WRITE},
	{"EV_TIMEOUT", EV_TIMEOUT},
	{"EV_SIGNAL", EV_SIGNAL},
	{"EV_PERSIST", EV_PERSIST},
	/* bufferevent */
	{NULL, 0}
};

void setNamedIntegers(lua_State* L, namedInteger* p) {
	while(p->name) {
		lua_pushinteger(L, p->value);
		lua_setfield(L, -2, p->name);
		p++;
	}
}

/* Verified ok */
#ifdef _WIN32
__declspec(dllexport) int luaopen_znetevent(lua_State* L)
{
	const char**pp;
	WORD wVersionRequested = MAKEWORD(2, 2);
	WSADATA wsaData;
	WSAStartup(wVersionRequested, &wsaData);
#else
extern int luaopen_znetevent(lua_State* L)
{
	const char**pp;
#endif
	//event_enable_debug_mode();
	pp=event_get_supported_methods();
	if (pp)
	{
		printf("LIBEVENT SUPPORT METHOD:");
		while(*pp)
		{
			printf(" %s",*pp);
			pp++;
		}
		printf("\n");
	}
	/* Register external items */
	event_callback_register(L);
	//event_buffer_register(L);
	//buffer_event_register(L);

	lua_settop(L, 0);
	/* Setup metatable */
	luaL_newmetatable(L, EVENT_BASE_MT);
	lua_newtable(L);
	luaL_register(L, NULL, base_funcs);
	lua_setfield(L, -2, "__index");
	lua_pushcfunction(L, znetevent_base_gc);
	lua_setfield(L, -2, "__gc");
	lua_pop(L, 1);

	luaL_register(L, "znetevent", funcs);
	setNamedIntegers(L, consts);

	return 1;
}
